package cn.com.lyj6851.auth.config;


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import cn.com.lyj6851.auth.error.AuthExceptionEntryPoint;
import cn.com.lyj6851.auth.error.MssWebResponseExceptionTranslator;
import cn.com.lyj6851.auth.util.MyRedisTokenStore;
import cn.com.lyj6851.common.properties.RootSecurityProperties;
/**
 * 认证服务器:
 * AuthorizationServerConfigurer 需要配置三个配置-重写几个方法：
 * 		ClientDetailsServiceConfigurer：用于配置客户详情服务，指定存储位置
 * 		AuthorizationServerSecurityConfigurer：定义安全约束
 * 		AuthorizationServerEndpointsConfigurer：定义认证和token服务
 *     
 *   1. 认证成功生成token,并存储到redis
 *	 2. JWT 生成
 */
@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter{

	@Autowired
	private DataSource dataSource;

	@Autowired
	private TokenStore tokenStore;
	
	@Autowired
	private RootSecurityProperties securityProperties;
	
	@Autowired
    private RedisConnectionFactory redisConnectionFactory;
	
	@Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
	
	// 注入认证管理器
	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
    private WebResponseExceptionTranslator customWebResponseExceptionTranslator;
	
    @Bean
    @ConditionalOnProperty(prefix = "micro.service.oauth2", name = "storeType", havingValue = "redis")
    public TokenStore redisTokenStore(){
        return new MyRedisTokenStore(redisConnectionFactory);
    }
    // matchIfMissing ：当tokenStore没有值的时候是否生效
    // 当tokenStore = jwt的时候或则tokenStore没有配置的时候使用下面的配置; 也就是默认使用jwt
    @Bean
    @ConditionalOnProperty(prefix = "micro.service.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    // 使用密签生成jwt token
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
        return converter;
    }
    
    @Bean
    public WebResponseExceptionTranslator webResponseExceptionTranslator(){
        return new MssWebResponseExceptionTranslator();
    }
    
    
	/**
	 * 认证令牌的入口点
	 * 配置使用jwt生成令牌
	 * 根据用户配置的选项,确定使用redis还是使用本地存储token
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(tokenStore) //指定token存储位置
				
		         .tokenServices(defaultTokenServices())
		        //.tokenStore(jwtTokenStore()) //或使用用本地存储token
		        .accessTokenConverter(jwtAccessTokenConverter);// jwt生成策略
		        //.exceptionTranslator(webResponseExceptionTranslator());//认证异常翻译
		endpoints.exceptionTranslator(customWebResponseExceptionTranslator);
	}
	/**
     * <p>注意，自定义TokenServices的时候，需要设置@Primary，否则报错，</p>
     * @return
     * 如使用内存以下注掉
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        //tokenServices.setClientDetailsService(clientDetails());
        tokenServices.setAccessTokenValiditySeconds(60*60*2); // token有效期自定义设置，默认12小时
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天，这里修改
        return tokenServices;
    }
	/**
	 * 客户端来向认证服务器获取签名的时候需要登录认证身份才能获取
	 * 因为客户端需要用密钥解密jwt字符串  NoOpPasswordEncoder.getInstance()
	 * security.passwordEncoder(NoOpPasswordEncoder.getInstance()); // 密码加密策略
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		
		security.tokenKeyAccess("permitAll()")
		//.tokenKeyAccess("permitAll()") //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
        .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
		.authenticationEntryPoint(new AuthExceptionEntryPoint());//自定义用于tokan校验失败返回信息
        security.allowFormAuthenticationForClients(); //主要是让/oauth/token支持client_id以及client_secret作登录认证
	}
	/*@Autowired
	private PasswordEncoder passwordEncoder;*/
	/**
	 * 1.从数据库获取并配置给哪些应用发令牌
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetails());
		//clients.jdbc(dataSource); //存储数据库
	}
	@Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
	
	
}
